﻿using DotNetOpenAuth.AspNet.Clients;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using DotNetOpenAuth.Messaging;
using Newtonsoft.Json.Linq;
using System.Web;
using System.Linq;

namespace Inovout.Web.Security
{
    public abstract class BaseOAuth2Client : OAuth2Client
    {
        private readonly string authorizeEndpoint;// = "https://accounts.google.com/o/oauth2/auth";
        private readonly string tokenServiceEndpoint;// = "https://accounts.google.com/o/oauth2/token";
        private readonly string userDataServiceEndpoint;// = "https://www.googleapis.com/oauth2/v1/userinfo";

        private readonly string clientId;
        private readonly string clientSecret;
        private readonly Dictionary<string, string> userDataMapping;
        private readonly string scope;
        protected readonly bool isAccessTokenHeader;

        private Dictionary<string, AccessTokenInfo> accessTokenMapping;
        //protected string code;
        //protected string accessToken;
        //protected string accessTokenExpiredTime;
        //protected string refreshToken;
        //protected string refreshTokenExpiredTime;

        protected BaseOAuth2Client(string providerName,
                                   string clientId, string clientSecret,
                                   Dictionary<string, string> userDataMapping,
                                   string authorizeEndpoint,
                                   string tokenServiceEndpoint, string userDataServiceEndpoint,
                                   bool isAccessTokenHeader = true,
                                   string scope = null)
            : base(providerName)
        {
            this.clientId = clientId;
            this.clientSecret = clientSecret;
            this.userDataMapping = userDataMapping;
            this.scope = scope;
            this.authorizeEndpoint = authorizeEndpoint;
            this.tokenServiceEndpoint = tokenServiceEndpoint;
            this.userDataServiceEndpoint = userDataServiceEndpoint;
            this.isAccessTokenHeader = isAccessTokenHeader;
            this.accessTokenMapping = new Dictionary<string, AccessTokenInfo>();
        }

        protected virtual void AppendServiceLoginUrl(UriBuilder builder)
        {
        }
        protected override Uri GetServiceLoginUrl(Uri returnUrl)
        {
            var builder = new UriBuilder(authorizeEndpoint);
            builder.AppendQueryArgument("client_id", this.clientId);
            builder.AppendQueryArgument("redirect_uri", returnUrl.AbsoluteUri);
            builder.AppendQueryArgument("response_type", "code");
            // builder.AppendQueryArgument("state", Guid.NewGuid().ToString());
            if (!string.IsNullOrEmpty(scope))
            {
                builder.AppendQueryArgument("scope", scope);
            }
            AppendServiceLoginUrl(builder);
            return builder.Uri;
        }

        protected virtual AccessTokenInfo BuildAccessToken(JObject accessTokenJson)
        {
            AccessTokenInfo accessTokenInfo = new AccessTokenInfo(accessTokenJson);
            accessTokenInfo.AccessToken = accessTokenJson["access_token"].ToString();
            accessTokenInfo.AccessTokenExpiredTime = accessTokenJson["expires_in"].ToString();

            if (accessTokenJson["refresh_token"] != null)
            {
                accessTokenInfo.RefreshToken = accessTokenJson["refresh_token"].ToString();
            }
            if (accessTokenJson["refresh_token_expiry_in"] != null)
            {
                accessTokenInfo.RefreshTokenExpiredTime = accessTokenJson["refresh_token_expiry_in"].ToString();
            }
            return accessTokenInfo;
        }

        protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
        {

            HttpClient httpClient = new HttpClient();
            var content = new FormUrlEncodedContent(new[] 
            {
                new KeyValuePair<string, string>("client_id", this.clientId),
                new KeyValuePair<string, string>("redirect_uri", returnUrl.AbsoluteUri),
                new KeyValuePair<string, string>("client_secret", this.clientSecret),
                new KeyValuePair<string, string>("code", authorizationCode),
                new KeyValuePair<string,string>("grant_type","authorization_code")
            });
            var result = httpClient.PostAsync(tokenServiceEndpoint, content);

            var tokenString = result.Result.Content.ReadAsStringAsync().Result;

            var accessTokenInfo = BuildAccessToken(ConvertToJObject(tokenString));
            accessTokenInfo.Code = authorizationCode;
            accessTokenMapping.Add(accessTokenInfo.AccessToken, accessTokenInfo);
            return accessTokenInfo.AccessToken;
        }

        protected virtual JObject ConvertToJObject(string tokenString)
        {
            return JObject.Parse(tokenString);
        }

        protected virtual void AppendUserDataQuery(UriBuilder builder, AccessTokenInfo accessTokenInfo)
        {
        }

        protected override IDictionary<string, string> GetUserData(string accessToken)
        {

            var builder = new UriBuilder(userDataServiceEndpoint);
            HttpClient httpClient = new HttpClient();
            if (this.isAccessTokenHeader)
            {
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",
              accessToken);
            }
            else
            {
                builder.AppendQueryArgument("access_token", accessToken);
            }
            AppendUserDataQuery(builder, accessTokenMapping[accessToken]);
            var result = httpClient.GetAsync(builder.Uri);
            var tokenString = result.Result.Content.ReadAsStringAsync().Result;
            var tokenJson = JObject.Parse(tokenString); 
            Dictionary<string, string> dictionary = new Dictionary<string, string>();

            foreach (var dic in userDataMapping)
            {
                dictionary.Add(dic.Key, tokenJson[dic.Value].ToString());
            }


            //dictionary.Add("id", userObject["user_identifier"]);
            //dictionary.Add("username", userObject["name"]);
            //dictionary.Add("email", userObject["email"]);
            //dictionary.Add("name", "microsoftClientUserData.Name");
            //dictionary.Add("link", "microsoftClientUserData.Link.AbsoluteUri");
            //dictionary.Add("gender", "microsoftClientUserData.Gender");
            //dictionary.Add("firstname", "microsoftClientUserData.FirstName");
            //dictionary.Add("lastname", "microsoftClientUserData.LastName");

            return dictionary;
        }

        protected AccessTokenInfo GetAccessTokenInfo(string accessToken)
        {
            return accessTokenMapping[accessToken];
        }
        public AccessTokenInfo GetAndClearAccessToken(string accessToken)
        {
            AccessTokenInfo accessTokenInfo = accessTokenMapping[accessToken];

            accessTokenMapping.Remove(accessToken);
            return accessTokenInfo;
        }

    }
}
